﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using JQuery.Net;
using System.IO;
using System.Web;
using System.Security.Cryptography;
using System.Web.Caching;
using JQuery.Net.Modules;
using HttpContext = Cymbi.Web.HttpContext;
using System.Net;

[assembly: WebResource(Resources.JQN.min, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.debug, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_extensions, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.net_debug, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_gadget, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_button, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_tabs, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_core, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_core_debug, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_widget, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_widget_debug, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_mouse, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_mouse_debug, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_position, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_position_debug, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_auto_complete, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_auto_complete_debug, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.JQN.ui_anytime, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.Css.ui_anytime, Resource.ContentTypes.css)]
[assembly: WebResource(Resources.Css.ui_gcm, Resource.ContentTypes.css)]
[assembly: WebResource(Resources.JQN.ui_menu, Resource.ContentTypes.javascript)]
[assembly: WebResource(Resources.Css.ui_menu, Resource.ContentTypes.css)]
[assembly: WebResource(Resources.JQN.ui_callback, Resource.ContentTypes.javascript)]

namespace JQuery.Net
{
    public class ResourceKey
    {
        internal ResourceKey(Resource Res)
        {
            Key = Res.Key;
            //CheckSum = Res.CheckSum;
        }

        public string Key { get; set; }
        /// <summary>
        /// Get the Current Check Sum of the Resource Key
        /// </summary>
        /// <remarks>
        /// this value can change in regards to a text resource dependant on weather the resource has been minimized or not.
        /// </remarks>
        //public string CheckSum
        //{
        //    get
        //    {
        //        return HttpContext.Current.ResourceManager().Resources.IsNotNull(String.Empty,(X) =>
        //            X[Key].CheckSum);
        //    }
        //}
    }

    public class Resource
    {
        #region constructor
        internal Resource()
            : base()
        {
            Ordinal = 0;
        }
        public Resource(String key, Type type, String contentType)
            : this(key, null, type, contentType) { }
        public Resource(String key, String uri, String contentType)
            : this(key, uri, null, contentType) { }
        internal Resource(String key, String uri, Type type, String contentType)
            : this()
        {
            if (key.IsNullOrEmpty())
            {   /// the key parameter is the only one that can not be null
                throw new ArgumentNullException("key");
            }

            this.key = key;
            this.uri = uri;
            this.type = type;
            this.contentType = contentType;
        }
        #endregion

        #region field variables
        private string uri = null;
        private Type type = null;
        protected string key = null;
        protected string contentType = null;
        #endregion

        #region properties
        public string Key { get { return key; } }
        public string Uri { get { return uri; } protected set { uri = value; } }
        public string ContentType { get { return contentType; } }
        internal bool ExcludeResource { get; set; }
        /// <summary>
        /// Get the content in a byte array
        /// text byte array using UTF8Encoding
        /// anything else would be a regular byte array. 
        /// </summary>
        public virtual byte[] ToByteArray
        {
            get
            {
                if (IsTextResource)
                {
                    #region Text Resource Code
                    UTF8Encoding UTF = new UTF8Encoding(true);
                    StringBuilder Content = new StringBuilder();

                    if (type != null)
                    {
                        Uri script = new Uri(String.Format("file:///{0}", key));

                        using (StreamReader RDR = new StreamReader(type.Assembly.GetManifestResourceStream(Path.GetFileName(script.AbsolutePath))))
                        {
                            Content.Append(RDR.ReadToEnd()).AppendLine();
                        }
                    }
                    else
                    {
                        if (!Uri.StartsWith("http"))
                        {
                            var Path = HttpContext.Current.Server.MapPath(Uri);

                            if (File.Exists(Path))
                            {
                                using (StreamReader oStream = File.OpenText(Path))
                                {
                                    Content.Append(oStream.ReadToEnd()).AppendLine();
                                }
                            }
                            else
                            {
                                throw new HttpException(404, String.Format("{0} File '{1}'.<br />\r\nCould not be found!", ContentType, Uri));
                            }
                        }
                        else
                        {   /// Cross site script caching
                            throw new NotImplementedException();
                        }
                    }

                    /// this is where we want to minimize the content if the resource is not already minimized
                    if (key.IsNotNull(false, (X) => X.Contains(".min")) || Uri.IsNotNull(false, (X) => X.Contains(".min")))
                    {
                        return UTF.GetBytes(Content.ToString());
                    }
                    else
                    {
                        if (HttpContext.Current.ResourceManager().IsMinimized)
                        {
                            byte[] oArrayOut = new byte[0];

                            if (ContentType.Eq(ContentTypes.javascript))
                            {
                                using (JSMinimizer oMin = new JSMinimizer(UTF.GetBytes(Content.ToString()), ContentType))
                                {
                                    oArrayOut = oMin.ToArray();
                                }
                            }
                            else if (ContentType.Eq(ContentTypes.css))
                            {
                                using (CSSMinimizer oMin = new CSSMinimizer(UTF.GetBytes(Content.ToString()), ContentType))
                                {
                                    oArrayOut = oMin.ToArray();
                                }
                            }

                            return oArrayOut;
                        }
                        else
                        {
                            return UTF.GetBytes(Content.ToString());
                        }
                    }

                    #endregion
                }
                else if (IsImageResource)
                {
                    #region Image Resource Code
                    byte[] results = null;

                    if (type != null)
                    {
                        Uri script = new Uri(String.Format("file:///{0}", key));

                        using (Stream oStream = type.Assembly.GetManifestResourceStream(Path.GetFileName(script.AbsolutePath)))
                        using (MemoryStream oMemory = new MemoryStream())
                        {
                            byte[] buffer = new byte[1024];

                            while (oStream.Read(buffer, 0, buffer.Length) > 0)
                            {
                                oMemory.Write(buffer, 0, buffer.Length);
                            }

                            results = oMemory.ToArray();
                        }
                    }
                    else
                    {
                        if (!Uri.StartsWith("http"))
                        {
                            var Path = HttpContext.Current.Server.MapPath(Uri);

                            if (File.Exists(Path))
                            {
                                using (FileStream oFile = new FileStream(Path, FileMode.Open, FileAccess.Read))
                                using (MemoryStream oMemory = new MemoryStream())
                                {
                                    byte[] buffer = new byte[1024];

                                    while (oFile.Read(buffer, 0, buffer.Length) > 0)
                                    {
                                        oMemory.Write(buffer, 0, buffer.Length);
                                    }

                                    results = oMemory.ToArray();
                                }
                            }
                            else
                            {
                                throw new HttpException(404, String.Format("{0} File '{1}'.<br />\r\nCould not be found!", ContentType, Uri));
                            }
                        }
                        else
                        {   /// Cross site script caching
                            throw new NotImplementedException();
                        }
                    }

                    return results;
                    #endregion
                }
                else return new byte[0];
            }
        }

        public string CheckSum
        {
            get
            {
                using (SHA256Managed oHash = new SHA256Managed())
                {
                    byte[] bytes = oHash.ComputeHash(ToByteArray);

                    StringBuilder Chk = new StringBuilder();

                    foreach (byte hex in bytes)
                    {
                        Chk.Append(hex.ToString("X2"));
                    }

                    return Chk.ToString();
                }
            }
        }

        public bool IsTextResource { get { return ContentType.StartsWith("text"); } }
        public bool IsImageResource { get { return ContentType.StartsWith("image"); } }
        #endregion

        #region members

        delegate string WebResourceUrl(Type type, string key);

        internal string RegisterResource(Page Page)
        {
            WebResourceUrl ResourceUrl = Page.ClientScript.GetWebResourceUrl;

            if (ContentType == ContentTypes.javascript)
            {
                Page.ClientScript.RegisterClientScriptInclude(Key, Page.ResolveUrl(String.Format("~/JQuery.Net/RESOURCE/{0}",
                    CheckSum,
                    Index)));

                return CheckSum;
            }
            else if (type != null)
            {
                return ResourceUrl(type, key);
            }
            else return uri;
        }

        public override string ToString()
        {
            return key;
        }
        #endregion

        public int Ordinal { get; set; }

        public int Index { get; internal set; }
    }

    public class CachedResource
        : Resource
    {
        private byte[] _bytes;

        #region constructor
        internal CachedResource(byte[] content, string type, Uri path)
            : base()
        {
            contentType = type;
            _bytes = content;
            key = CheckSum;
            Uri = path.AbsolutePath;
        }
        #endregion

        #region properties
        public override byte[] ToByteArray
        {
            get
            {
                byte[] oArrayOut = _bytes;

                if (HttpContext.Current.ResourceManager().IsMinimized)
                {
                    if (ContentType.Eq(ContentTypes.javascript))
                    {
                        using (JSMinimizer oMin = new JSMinimizer(oArrayOut, ContentType))
                        {
                            oArrayOut = oMin.ToArray();
                        }
                    }
                    else if (ContentType.Eq(ContentTypes.css))
                    {
                        using (CSSMinimizer oMin = new CSSMinimizer(oArrayOut, ContentType))
                        {
                            oArrayOut = oMin.ToArray();
                        }
                    }
                }

                return oArrayOut;
            }
        }
        #endregion

        #region CachedResource Members
        public static CachedResource Create(Uri path, byte[] content, string contentType, CacheDependency dependency, CacheItemPriority priority)
        {
            return Create(path, content, contentType, dependency, priority, null);
        }
        public static CachedResource Create(Uri path, byte[] content, string contentType, CacheDependency dependency, CacheItemPriority priority, CacheItemRemovedCallback callback)
        {
            CachedResource Res = new CachedResource(content, contentType, path);

            HttpContext.Current.Cache.Add(Res.CheckSum, Res, dependency,
                        Cache.NoAbsoluteExpiration,
                        new TimeSpan(0, 30, 0),
                        priority,
                        callback);

            return Res;
        }
        #endregion
    }

    public class Resources
    {
        #region constants
        private static readonly string INDEX = "INDEX";
        public struct JQN
        {
            internal const string min = "JQuery.Net.Resources.Scripts.jquery-1.4.2.min.js";
            internal const string debug = "JQuery.Net.Resources.Scripts.jquery-1.4.2.js";
            internal const string net = "JQuery.Net.Resources.Scripts.jquery.net-1.0.min.js";
            internal const string net_debug = "JQuery.Net.Resources.Scripts.jquery.net-1.0.js";
            internal const string ui_extensions = "JQuery.Net.Resources.Scripts.jquery.ui.extensions.js";
            internal const string ui_gadget = "JQuery.Net.Resources.Scripts.jquery.net.gadget-1.0.js";
            internal const string ui_core = "JQuery.Net.Resources.Scripts.jquery.ui.core.min.js";
            internal const string ui_core_debug = "JQuery.Net.Resources.Scripts.jquery.ui.core.js";
            internal const string ui_widget = "JQuery.Net.Resources.Scripts.jquery.ui.widget.min.js";
            internal const string ui_widget_debug = "JQuery.Net.Resources.Scripts.jquery.ui.widget.js";
            internal const string ui_mouse = "JQuery.Net.Resources.Scripts.jquery.ui.mouse.min.js";
            internal const string ui_mouse_debug = "JQuery.Net.Resources.Scripts.jquery.ui.mouse.js";
            internal const string ui_position = "JQuery.Net.Resources.Scripts.jquery.ui.position.min.js";
            internal const string ui_position_debug = "JQuery.Net.Resources.Scripts.jquery.ui.position.js";
            internal const string ui_auto_complete = "JQuery.Net.Resources.Scripts.jquery.ui.autocomplete.min.js";
            internal const string ui_auto_complete_debug = "JQuery.Net.Resources.Scripts.jquery.ui.autocomplete.js";
            internal const string ui_anytime = "JQuery.Net.Resources.Scripts.jquery.ui.anytime.js";
            internal const string ui_menu = "JQuery.Net.Resources.Scripts.jquery.ui.menu.js";
            internal const string ui_button = "JQuery.Net.Resources.Scripts.jquery.ui.button.js";
            internal const string ui_tabs = "JQuery.Net.Resources.Scripts.jquery.ui.tabs.js";
            internal const string ui_callback = "JQuery.Net.Resources.Scripts.jquery.ui.callback.js";
        }
        public struct Css
        {
            internal const string ui_anytime = "JQuery.Net.Resources.Css.jquery.ui.anytime.css";
            internal const string ui_gcm = "JQuery.Net.Resources.Css.jquery.ui.gcm.css";
            internal const string ui_menu = "JQuery.Net.Resources.Css.jquery.ui.menu.css";
            //internal const string ui_gadget = "JQuery.Net.Resources.Css.jqn.gadget.css";
        }
        #endregion

        #region constructor
        private Resources()
        {
            resources = new Dictionary<ResourceKey, Resource>();
        }
        #endregion

        internal static Resources Create()
        {
            Resources _resources = new Resources();

            Type _resource = typeof(Resource);

#if DEBUG
            _resources.AddRange(new Resource[]
            {
                new Resource(JQN.debug, null, _resource, Resource.ContentTypes.javascript) { ExcludeResource = true, Ordinal = -3 },
                new Resource(JQN.ui_core_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -2 },
                new Resource(JQN.ui_widget_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -2 },
                new Resource(JQN.ui_mouse_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -2 },
                new Resource(JQN.ui_position_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -2 },
                new Resource(JQN.net_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -1 },
                new Resource(JQN.ui_auto_complete_debug, null, _resource, Resource.ContentTypes.javascript),
                new Resource(JQN.ui_anytime, null, _resource, Resource.ContentTypes.javascript),
                new Resource(Css.ui_anytime, null, _resource, Resource.ContentTypes.css)
            });
#else
            _resources.AddRange(new Resource[]
            {
                new Resource(JQN.debug, null, _resource, Resource.ContentTypes.javascript) { ExcludeResource = true, Ordinal = -3 },
                new Resource(JQN.ui_core_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -2 },
                new Resource(JQN.ui_widget_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -2 },
                new Resource(JQN.ui_mouse_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -2 },
                new Resource(JQN.ui_position_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -2 },
                new Resource(JQN.net_debug, null, _resource, Resource.ContentTypes.javascript) { Ordinal = -1 },
                new Resource(JQN.ui_auto_complete_debug, null, _resource, Resource.ContentTypes.javascript),
                new Resource(JQN.ui_anytime, null, _resource, Resource.ContentTypes.javascript),
                new Resource(Css.ui_anytime, null, _resource, Resource.ContentTypes.css)
            });
#endif

            return _resources;
        }

        #region indexer
        public Resource this[string name]
        {
            get
            {   /// we need to determine if the resource is a cached resource
                Resource oResource = null;
                ResourceKey oKey = null;

                if (IsResourceRegistered(name, ref oKey) || IsResourceCheckSumRegistered(name, ref oKey) || IsResourceInitialized(name, ref oKey))
                {
                    oResource = resources[oKey];

                    if (oResource != null || IsResourceInCache(oKey.Key, ref oResource))
                    {   /// the reason for this check is because the resource could have become invalid and that is what 
                        /// this check is for.
                        return oResource;
                    }
                }
                else if (!IsResourceInCache(name, ref oResource))
                {   // if we are here then the resource is not yet available and may have to be built
                    Uri
                        BaseURI = new Uri(HttpContext.Current.BaseUri());
                    String
                        relativeUrl = name.Split('|')[0].DecodeFrom64();

                    if (relativeUrl.EndsWith(".aspx"))
                    {
                        WebRequest oRequest = WebRequest.Create(new Uri(BaseURI, relativeUrl));

                        oRequest.Credentials = CredentialCache.DefaultCredentials;

                        HttpWebResponse oResponse = (HttpWebResponse)oRequest.GetResponse();
                    }

                    oResource = this[name];
                }

                return oResource;
            }
        }

        public String this[Uri Uri]
        {
            get
            {
                Dictionary<string, string> RESOURCEINDEX = ((Dictionary<string, string>)HttpContext.Current.Cache.Get(INDEX));

                string KEY = Uri.AbsolutePath.ToLower();

                if (RESOURCEINDEX.IsNotNull(false, (X) => X.ContainsKey(KEY)))
                {
                    String oTheKey = RESOURCEINDEX[KEY];
                    /// if the resource is valid return the key
                    if ((this)[oTheKey] != null)
                        return oTheKey;
                    else
                        RESOURCEINDEX.Remove(KEY);
                }
                
                return String.Empty;
            }

            set
            {
                Dictionary<string, string> RESOURCEINDEX = ((Dictionary<string, string>)HttpContext.Current.Cache.Get(INDEX));

                string KEY = Uri.AbsolutePath.ToLower();

                if (RESOURCEINDEX.IsNotNull(false, (X) => X.ContainsKey(KEY)))
                {
                    RESOURCEINDEX[KEY] = value;
                }
                else
                {
                    RESOURCEINDEX = RESOURCEINDEX.IsNotNull(new Dictionary<string, string>(), (X) => X);

                    RESOURCEINDEX.Add(KEY, value);

                    HttpContext.Current.Cache.Add(INDEX, RESOURCEINDEX, null,
                        Cache.NoAbsoluteExpiration,
                        Cache.NoSlidingExpiration,
                        CacheItemPriority.NotRemovable,
                        (key, obj, removed) =>
                        {   /// this is a delegate for reason removed
                        });
                }
            }
        }
        #endregion

        #region field variables
        private Dictionary<ResourceKey, Resource> resources = null;
        #endregion

        #region properties
        /// <summary>
        /// Get a reference to any available CSS files.
        /// </summary>
        public IEnumerable<Resource> CSS
        {
            get
            {
                return resources.AsQueryable()
                    .Select((X) =>
                        X.Value)
                    .Where((X) =>
                        X.ContentType == Resource.ContentTypes.css);
            }
        }
        #endregion

        #region members
        public bool IsResourceInCache(string name)
        {
            return HttpContext.Current.Cache.Get(name) != null;
        }

        public bool IsResourceInCache(string name, ref Resource resource)
        {
            bool result = false;

            if (result = IsResourceInCache(name))
            {
                resource = (Resource)HttpContext.Current.Cache.Get(name);
            }

            return result;
        }

        //public bool IsResourceCheckSumRegistered(String checkSum)
        //{
        //    ResourceKey Key = resources.Keys.SingleOrDefault((X) => X.CheckSum == checkSum);

        //    return Key != null;
        //}

        //public bool IsResourceCheckSumRegistered(String checkSum, ref ResourceKey key)
        //{
        //    key = resources.Keys.SingleOrDefault((X) => X.CheckSum == checkSum);

        //    return key != null;
        //}

        public bool IsResourceRegistered(String name)
        {
            ResourceKey Key = resources.Keys.SingleOrDefault((X) => X.Key == name);

            return Key != null;
        }

        public bool IsResourceRegistered(String name, ref ResourceKey key)
        {
            key = resources.Keys.SingleOrDefault((X) => X.Key == name);

            return key != null;
        }

        public bool IsResourceInitialized(string name, ref ResourceKey key)
        {
            string[] value = name.Split('|');
            string
                uri = value[0].DecodeFrom64();
            int
                index = 0;

            if (value.Length > 1)
            {

                Int32.TryParse(value[1], out index);

                key = resources.SingleOrDefault((X) =>
                            X.Value.Uri.IsNotNullOrEmpty() && X.Value.Uri.Eq(uri) && X.Value.Index == index)
                        .IsNotNull((X) =>
                            X.Key);
            }

            return key != null;
        }

        public void AddRange(IEnumerable<Resource> collection)
        {
            foreach (Resource Res in collection)
            {
                Add(Res);
            }
        }

        public void Add(Resource item)
        {
            if (!IsResourceRegistered(item.Key))
            {
                if (JQNModule.Resources != null && item.Ordinal < 0)
                {   /// this is to ensure that the resource is loaded after the jQuery core and jQuery UI scripts
                    item.Ordinal = 0;
                }
                // we need to track the order of the resource for retrieval
                item.Index = resources.Count((X) =>
                     X.Value.Uri.IsNotNullOrEmpty() && X.Value.Uri.Eq(item.Uri)) + 1;

                resources.Add(new ResourceKey(item), item);
            }
        }

        public void Remove(ResourceKey Item)
        {
            if (IsResourceRegistered(Item.Key))
            {
                resources.Remove(Item);
            }
        }
        #endregion

        internal IEnumerable<Resource> ToList()
        {
            return resources.Select((X) =>
                X.Value);
        }
    }


}
